Skip to content

前端测试服 Docker + Nginx 发布优化:从 docker build 到 SFTP 直接发布

纯 Vue + Nginx 静态前端,每次发测试服都 docker build 没有必要。把 www 从镜像里拿出来,挂到宿主机目录上,之后只 SFTP 上传即可。

一、背景

旧流程:

text
本地构建 → SFTP 上传 → SSH 登录 → 执行 docker.sh
  → 停容器 → 删镜像 → docker build → 再启动

docker.sh 大致是:

bash
docker stop <容器>
docker rm <容器>
docker rmi <镜像>
docker build -t <镜像> .
docker run ...

静态文件变了,镜像层却要整份重做。目标流程:

text
本地 npm run build → 生成 www/ → VS Code SFTP 上传 www/ → Nginx 直接读 → 完成

日常发版只上传文件,不再执行 Docker 命令。


二、原来的 Dockerfile

dockerfile
FROM nginx:stable-alpine

COPY ./www/ /var/www/
COPY nginx.conf /etc/nginx/conf.d/conf.template

ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ENV NGINX_HOST=localhost \
    NGINX_PORT=80

CMD envsubst '$NGINX_HOST $NGINX_PORT' < /etc/nginx/conf.d/conf.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'

根因是这一行:

dockerfile
COPY ./www/ /var/www/

文件被打进镜像:

text
本地 www/  →  docker build  →  镜像  →  容器 /var/www

www 一变就必须重新 docker build。Volume 挂上之后,运行时会盖住镜像里这份目录,COPY ./www/ 实际不再被用到;想瘦镜像可以以后再删掉它,但那属于改 Dockerfile,要走下面第十四节的重建流程。


三、原来的 Nginx 配置

nginx
location / {
    root /var/www;
    try_files $uri /index.html;
    index index.html;

    gzip on;
    gzip_types text/css text/javascript application/x-javascript application/json;
}

Nginx 读的就是容器内 /var/www。改造只做一件事:让这个路径对应宿主机上的 www


四、Docker Volume 挂载

服务器目录:

text
/home/bjobd/obd-macro-monitor-web/main-web
└── www      # 前端静态资源

映射:

text
宿主机  /home/bjobd/obd-macro-monitor-web/main-web/www
              ↓  bind mount
容器    /var/www

启动(首次,或容器被删后):

bash
docker run -d \
  -v /home/bjobd/obd-macro-monitor-web/main-web/www:/var/www \
  -v /home/bjobd/obd-macro-monitor-web/main-web/log:/var/log/nginx \
  -p 9077:80 \
  --name obd-macro-monitor-main-web \
  --restart unless-stopped \
  bjobd/obd-macro-monitor-main-web

关键参数是 -v 宿主机www:/var/www。这里用的是 bind mount(绑宿主机路径),不是匿名 Volume。


五、改造前后对比

旧方案Volume 挂载
文件怎么进容器wwwdocker build → 镜像 → 容器宿主机 www → 挂载 → 容器 /var/www
日常发版重建镜像、重建容器只改宿主机文件
要不要重启 Nginx一般不要

六、VS Code SFTP 配置

.vscode/sftp.json

json
{
    "name": "测试服",
    "host": "192.200.125.79",
    "protocol": "sftp",
    "port": 22,
    "username": "deploy",
    "privateKeyPath": "~/.ssh/id_ed25519",
    "remotePath": "/home/bjobd/obd-macro-monitor-web/main-web/www",
    "context": "./www",
    "uploadOnSave": false,
    "useTempFile": true,
    "openSsh": false
}

几点:

  • 用密钥,不要把密码写进仓库。
  • context 是本地构建产物目录,remotePath 是服务器上被挂载的 www。上传的是 www 里面的 index.htmlassets/,不要再套一层 www/www/
  • useTempFile: true:先传临时文件再改名,降低上传过程中 Nginx 读到半截文件的概率。

本地步骤:

bash
npm run build

然后用插件把 www/ 同步到 remotePath


七、为什么容器跑着也能上传

改的不是容器可写层,改的是宿主机磁盘。容器里的 /var/www 只是同一块目录的挂载点:

text
VS Code SFTP

宿主机 /home/bjobd/obd-macro-monitor-web/main-web/www
    ↓  bind mount
容器 /var/www

Nginx 读文件对外服务

因此日常发版 不必重启容器。两点例外:

  1. Nginx 若开了 open_file_cache,可能短期内仍吐旧 inode,这时 docker exec <容器> nginx -s reload 即可,仍然不用重建镜像。
  2. 浏览器会缓存 index.html / 带 hash 的资源。Vue 构建产物文件名变了一般没问题;若页面仍是旧的,先强制刷新。

八、权限问题

deploy 上传时出现:

text
Error: Permission denied

项目在 /home/bjobd/... 下,属主是 bjobd。SFTP 登录的是 deploy。Linux 进入目录需要沿途每一层都有 x(执行)权限,写入 www 还要有写权限。缺任何一层都会 Permission denied。


九、创建独立部署账号

bash
sudo useradd -m -s /bin/bash deploy
sudo passwd deploy
id deploy

不要把 deploy 加进 docker 组,除非这个账号还要在服务器上执行 docker 命令。日常 SFTP 上传不需要 Docker 权限;能操作 Docker 等于能拿到 root。


十、目录权限

lsmain-web+ 表示 ACL。家目录不能直接 chmod 777,用 ACL 只给 deploy 穿过路径、并让 www 归它所有:

bash
# 穿过 bjobd 家目录(只要 x,不必给读)
sudo setfacl -m u:deploy:--x /home/bjobd
sudo setfacl -m u:deploy:rx /home/bjobd/obd-macro-monitor-web
sudo setfacl -m u:deploy:rx /home/bjobd/obd-macro-monitor-web/main-web

# www 交给 deploy,SFTP 才能覆盖构建产物
sudo chown -R deploy:deploy /home/bjobd/obd-macro-monitor-web/main-web/www
sudo chmod -R u+rwX,g+rX,o+rX /home/bjobd/obd-macro-monitor-web/main-web/www

# Nginx 进程要能读这些文件。alpine 镜像里 nginx 通常以 nginx 用户运行,
# 上面 o+rX 已覆盖;若容器内用户读不到,再补:
# sudo chmod -R o+rX /home/bjobd/obd-macro-monitor-web/main-web/www

预期:

text
drwxrwxr-x+  4 bjobd  bjobd  ... main-web
drwxr-xr-x   3 deploy deploy ... www
-rw-r--r--   1 deploy deploy ... index.html

getfacl /home/bjobd 应能看到 user:deploy:--x

容器内 Nginx 读的是挂载进来的同一批文件。属主是 deploy 没关系,只要 others 或 ACL 允许读取。


十一、校验挂载是否生效

bash
docker inspect obd-macro-monitor-main-web \
  --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'

预期:

text
/home/bjobd/obd-macro-monitor-web/main-web/www -> /var/www
/home/bjobd/obd-macro-monitor-web/main-web/log -> /var/log/nginx

十二、验证文件实时同步

bash
ls -li /home/bjobd/obd-macro-monitor-web/main-web/www/index.html
docker exec obd-macro-monitor-main-web ls -li /var/www/index.html
curl -s http://127.0.0.1:9077/index.html | head

两边 inode 应相同。SFTP 上传新文件后再 curl,响应体应是新内容。


十三、日常发布

text
本地开发 → npm run build → VS Code SFTP 同步 www → 完成

Nginx 容器一直跑着。不再 SSH、不再 docker build、不再删镜像。


十四、什么时候还要动 Docker

普通前端发版:不需要。

场景做什么
首次部署 / 容器被删docker run(带上 -v
nginx.conf配置在镜像里的话要重建镜像并换容器;若同样改成挂载,只需把文件放到挂载路径后 nginx -s reload
改 Dockerfile / 基础镜像 / 时区脚本docker build 后再换容器

十五、最终架构

text
                 本地开发
                    │ npm run build

                  www/
                    │ VS Code SFTP

┌──────────────────────────────────────────┐
│                 测试服务器               │
│  /home/bjobd/obd-macro-monitor-web/      │
│              main-web/www                │
│                    │ bind mount          │
│                    ↓                     │
│              容器 /var/www               │
│                    ↓                     │
│              Nginx  :9077                │
└────────────────────┼─────────────────────┘

                  浏览器

测试服发版两步:npm run build,再 SFTP 上传 www

最近更新